package com.louis.kitty.admin.aspect;

import javax.servlet.http.HttpServletRequest;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSONObject;
import com.louis.kitty.admin.model.SysLog;
import com.louis.kitty.admin.sevice.SysLogService;
import com.louis.kitty.admin.util.HttpUtils;
import com.louis.kitty.admin.util.IPUtils;
import com.louis.kitty.admin.util.SecurityUtils;

import java.lang.reflect.Method;


/**
 * 系统日志，切面处理类，记录日志
 *
 * 知识点1：
 * @Aspect
 * 作用是把当前类标识为一个切面供容器读取
 *
 * 知识点2：
 * @Pointcut
 * Pointcut是植入Advice的触发条件。
 * 每个Pointcut的定义包括2部分，一是表达式，二是方法签名。
 * 方法签名必须是 public及void型。
 * 可以将Pointcut中的方法看作是一个被Advice引用的助记符，因为表达式不直观，因此我们可以通过方法签名的方式为此表达式命名。
 * 因此Pointcut中的方法只需要方法签名，而不需要在方法体内编写实际代码。
 *
 * execution(* com.xyz.service.*.*(..)) 定义在service包里的任意方法的执行
 * execution(* com.xyz.service ..*.*(..)) 定义在service包和所有子包里的任意类的任意方法的执行
 *
 * 知识点3：
 * @Around
 * 环绕增强，相当于MethodInterceptor
 */
@Aspect
@Component
public class SysLogAspect {
	
	@Autowired
	private SysLogService sysLogService;

	/**
	 * 记录任意在service包里的任意方法的执行情况，不包括子包
	 */
	@Pointcut("execution(* com.louis.kitty.*.service.*.*(..))")
	public void logPointCut() { 
		
	}

	/**
	 * 在已声明的切点处添加增强advice
	 */
	@Around("logPointCut()")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		long beginTime = System.currentTimeMillis();
		/**
		 * 环绕通知=前置+目标方法执行+后置通知，proceed方法就是用于启动目标方法执行的
		 * 暴露出proceed这个方法，就能支持 aop:around 这种切面，能决定是否走代理链还是走自己拦截的其他逻辑
		 * 而其他的几种切面只需要用到JoinPoint，这也是环绕通知和前置、后置通知方法的一个最大区别
		 * ProceedingJoinPoint is only supported for around advice
		 */

		Object result = point.proceed();
		// 执行时长(毫秒)
		long time = System.currentTimeMillis() - beginTime;
		// 保存日志
		saveSysLog(point, time);
		return result;
	}

	/**
	 * 保存日志
	 */
	private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
		String userName = SecurityUtils.getUsername();

		/**
		 * 日志操作不做日志记录
		 * joinPoint.getTarget()的作用：获取切入点所在目标对象
		 */

		if(joinPoint.getTarget() instanceof SysLogService) {
			return ;
		}

		/**
		 * getSignature()的作用：获取到这样的信息 :修饰符+ 包名+组件名(类名) +方法名
		 */
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		SysLog sysLog = new SysLog();
		Method method = signature.getMethod();

		/**
		 * 获取swagger注解的描述作为用户操作描述
		 */
		if (method != null)
		{
			ApiOperation apiLog=  method.getAnnotation(ApiOperation.class);
			if(null != apiLog){
				sysLog.setOperation(apiLog.value());
			}
		}

		// 请求的类名
		String className = joinPoint.getTarget().getClass().getName();
		// 请求的方法名
		String methodName = signature.getName();
		sysLog.setMethod(className + "." + methodName + "()");

		// 请求的参数
		Object[] args = joinPoint.getArgs();
		try{
			String params = JSONObject.toJSONString(args[0]);
			if(params.length() > 200) {
				params = params.substring(0, 200) + "...";
			}
			sysLog.setParams(params);
		} catch (Exception e){
			e.printStackTrace();
		}

		// 获取request
		HttpServletRequest request = HttpUtils.getHttpServletRequest();
		// 设置IP地址
		sysLog.setIp(IPUtils.getIpAddr(request));

		// 用户名
		sysLog.setUserName(userName);
		
		// 执行时长(毫秒)
		sysLog.setTime(time);
		
		// 保存系统日志
		sysLogService.save(sysLog);
	}

	
}
